﻿using System;
using System.Collections.Concurrent;
using CS.Diagnostics;

namespace CS.Caching
{
    /// <summary>
    /// 对象缓存器
    /// <remarks>
    /// 不同的类型将会转换为object存储于内存，有转换效率损耗
    /// </remarks>
    /// </summary>
    public static class ObjectCache
    {
        private static readonly ConcurrentDictionary<string, object> cachedDic = new ConcurrentDictionary<string, object>();
        private static readonly object syncLock = new object();

        /// <summary>
        /// 清空所有缓存项
        /// </summary>
        /// <returns></returns>
        public static void Clear()
        {
            lock (syncLock)
            {
                cachedDic.Clear();
            }
        }

        /// <summary>
        /// 清空某缓存项
        /// </summary>
        /// <returns></returns>
        public static void Clear(string key)
        {
            object o;
            cachedDic.TryRemove(key, out o);
        }

        /// <summary>
        /// 缓存项的数量
        /// </summary>
        public static int Count
        {
            get
            {
                lock (syncLock)
                {
                    return cachedDic.Count;
                }
            }
        }

        /// <summary>
        /// 获取缓存的值
        /// <remarks>默认5分钟缓存</remarks>
        /// </summary>
        /// <typeparam name="T">缓存值类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="func">返回值的方法委托</param>
        /// <returns></returns>
        public static T GetValue<T>(string key, Func<T> func)
        {
            var val = GetValue(key, 300, func);
            return val;
        }

        /// <summary>
        /// 获取缓存的值
        /// </summary>
        /// <typeparam name="T">缓存值类型</typeparam>
        /// <param name="useKey">缓存键</param>
        /// <param name="seconds">失效时间</param>
        /// <param name="func">返回值的方法委托</param>
        /// <returns></returns>
        public static T GetValue<T>(string useKey, int seconds, Func<T> func)
        {
            object o;
            T val;
            CacheItem<T> item;
            var key = string.Format("{0}_{1}", useKey, func.Method.MetadataToken);
            var exists = cachedDic.TryGetValue(key, out o);
            if (!exists)
            {
                Tracer.Debug("0_key={0}的对象不存在", key);
                val = func.Invoke();
                item = new CacheItem<T>(val, seconds);
                cachedDic.TryAdd(key, item);//不论成败
                return val;
            }
            item = o as CacheItem<T>;
            if (item == null)
            {
                Tracer.Error("key={0}的对象无法转换到目的类型T={1}", key, typeof(T));
                val = func.Invoke();
                item = new CacheItem<T>(val, seconds);
                cachedDic[key] = item;
                return val;
            }
            if (item.Expired)
            {
                Tracer.Debug("key={0}的对象存储已过期", key);
                val = func.Invoke();
                item.Value = val;
                cachedDic[key] = item;
                return val;
            }
            return item.Value;
        }

        /// <summary>
        /// 获取缓存的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="useKey"></param>
        /// <param name="seconds"></param>
        /// <param name="func"></param>
        /// <param name="param">调用方法的字符串参数</param>
        /// <returns></returns>
        public static T GetValue<T>(string useKey, int seconds, Func<string, T> func, string param)
        {
            object o;
            T val;
            CacheItem<T> item;
            var key = string.Format("1_{0}_{1}", useKey, func.Method.MetadataToken);
            var exists = cachedDic.TryGetValue(key, out o);
            if (!exists)
            {
                Tracer.Debug("key={0}的对象不存在", key);
                val = func.Invoke(param);
                item = new CacheItem<T>(val, seconds);
                cachedDic.TryAdd(key, item);//不论成败
                return val;
            }
            item = o as CacheItem<T>;
            if (item == null)
            {
                Tracer.Error("key={0}的对象无法转换到目的类型T={1}", key, typeof(T));
                val = func.Invoke(param);
                item = new CacheItem<T>(val, seconds);
                cachedDic[key] = item;
                return val;
            }
            if (item.Expired)
            {
                Tracer.Debug("key={0}的对象存储已过期", key);
                val = func.Invoke(param);
                item.Value = val;
                cachedDic[key] = item;
                return val;
            }
            return item.Value;
        }


        internal static Random Rnd = new Random();//泛型里的静态成员非同类型间不共享
    }

    /// <summary>
    /// 缓存项
    /// <remarks>缓存的时间至少是2秒钟，且设定的时间有有1/4时间是随时出来的（避免同时过期同时取数据）</remarks>
    /// </summary>
    /// <typeparam name="T">缓存的类型</typeparam>
    public class CacheItem<T>
    {
        /// <summary>
        /// 创建一个新缓存项
        /// </summary>
        /// <param name="value">值</param>
        /// <param name="seconds">有效时间，秒</param>
        public CacheItem(T value, int seconds)
        {
            var quarters = seconds / 4 + 1;
            _seconds = 3 * quarters + ObjectCache.Rnd.Next(1, quarters);
            ExpiredTime = DateTime.Now.AddSeconds(_seconds);
            Value = value;
        }

        /// <summary>
        /// 过期时间
        /// </summary>
        public DateTime ExpiredTime { get; set; }

        /// <summary>
        /// 是否过期
        /// </summary>
        public bool Expired
        {
            get { return DateTime.Now > ExpiredTime; }
        }

        private readonly int _seconds;
        private T _val;
        /// <summary>
        /// 缓存内容
        /// <remarks>缓存过期时返回为null</remarks>
        /// </summary>
        public T Value
        {
            get
            {
                return Expired ? default(T) : _val;
            }
            set
            {
                ExpiredTime = DateTime.Now.AddSeconds(_seconds);
                _val = value;
            }
        }

    }
}